package cn.kivensoft.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/** 获取指定包下的所有类，自动循环扫描下级目录
 * @author Kiven Lee
 * @version 1.0.0
 */
final public class ScanPackage {
	private static final String CLASS_NAME = ".class";

	public interface OnRead {
		void accept(InputStream in) throws IOException;
	}

	/** 返回指定包下面的所有类
	 * @param pkgName 包名称
	 * @param recursive 是否递归查找
	 * @param predicate 条件过滤表达式，对类名进行条件过滤
	 */
	public static void getFiles(String pkgName, boolean recursive, Predicate<String> predicate, OnRead onRead) {
		// 获取包的名字 并进行替换
		String pkgDir = pkgName.replace('.', '/');
		// 定义一个枚举的集合 并进行循环来处理这个目录下的things
		Enumeration<URL> dirs;
		try {
			dirs = Thread.currentThread().getContextClassLoader().getResources(pkgDir);
		} catch (IOException e) { return; }

		// 循环迭代下去
		while (dirs.hasMoreElements()) {

			// 获取下一个元素
			URL url = dirs.nextElement();
			// 得到协议的名称
			String protocol = url.getProtocol();
			// 如果是以文件的形式保存在服务器上
			if ("file".equals(protocol)) {
				try {
					// 获取包的物理路径
					String path = URLDecoder.decode(url.getFile(), "UTF-8");
					// 以文件的方式扫描整个包下的文件 并添加到集合中
					findByFile("", path, recursive, predicate, onRead);
				} catch (UnsupportedEncodingException ignored) { }
			}
			// 如果是jar包文件
			else if ("jar".equals(protocol))
				findByJar(url, pkgDir, recursive, predicate, onRead);
		}
	}

	/** 返回指定包下面的所有类
	 * @param pkgName 包名称
	 * @return 找到的类对象列表
	 */
	public static List<Class<?>> getClasses(String pkgName) {
		return getClasses(pkgName, true, false, null);
	}

	/** 返回指定包下面的所有类
	 * @param pkgName 包名称
	 * @param recursive 是否递归查找
	 * @return 找到的类对象列表
	 */
	public static List<Class<?>> getClasses(String pkgName, boolean recursive) {
		return getClasses(pkgName, recursive, false, null);
	}

	/** 返回指定包下面的所有类
	 * @param pkgName 包名称
	 * @param recursive 是否递归查找
	 * @param pred 条件过滤表达式，对类名进行条件过滤
	 * @return 找到的类对象列表
	 */
	public static ArrayList<Class<?>> getClasses(String pkgName, boolean recursive, Predicate<String> pred) {
		return getClasses(pkgName, recursive, false, pred);
	}

	/** 返回指定包下面的所有类
	 * @param pkgName 包名称
	 * @param recursive 是否递归查找
	 * @param includeInnerClass 是否包含匿名类
	 * @param pred 条件过滤表达式，对类名进行条件过滤
	 * @return 找到的类对象列表
	 */
	public static ArrayList<Class<?>> getClasses(String pkgName, boolean recursive,
			boolean includeInnerClass, Predicate<String> pred) {
		// 第一个class类的集合
		ArrayList<Class<?>> result = new ArrayList<>();
		StringBuilder sb = new StringBuilder(pkgName).append('.');
		int sb_len = sb.length(), cls_len = CLASS_NAME.length();
		getFiles(pkgName, recursive, s -> {
			// 不是".class"结尾, 或者带有"$"符号(内部类或匿名类), 不进行处理
			if (!includeInnerClass && s.indexOf('$') != -1 || !s.endsWith(CLASS_NAME))
				return false;
			sb.setLength(sb_len);
			sb.append(s).setLength(sb.length() - cls_len);
			for (int i = 0, n= sb.length(); i < n; ++i)
				if (sb.charAt(i) == '/') sb.setCharAt(i, '.');
			String clsName = sb.toString();
			if (pred == null || pred.test(clsName)) {
				try {
					result.add(Class.forName(clsName));
				} catch (ClassNotFoundException ignored) { }
			}
			return false;
		}, null);
		return result;
	}

	// 在系统目录下查找
	private static <T> void findByFile(String pkgPath, String fullPath, boolean recursive, Predicate<String> pred, OnRead onRead) {

		// 获取此包的目录 建立一个File
		File dir = new File(fullPath);
		// 如果不存在或者 也不是目录就直接返回
		if (!dir.exists() || !dir.isDirectory()) return;

		// 如果存在 就获取包下的所有文件 包括目录
		// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
		File[] files = dir.listFiles();
		if (files != null && files.length > 0) {
			StringBuilder sb = new StringBuilder();
			if (!pkgPath.isEmpty()) sb.append(pkgPath).append('/');
			int sb_len = sb.length();

			// 循环所有文件
			for (File file : files) {
				sb.setLength(sb_len);
				sb.append(file.getName());
				String fname = sb.toString();
				if (file.isFile()) {
					if ((pred == null || pred.test(fname)) && onRead != null)
						try (InputStream in = new FileInputStream(file)) {
							onRead.accept(in);
						} catch (IOException ignored) {
						}
				}
				// 如果是目录且
				else if (file.isDirectory()) {
					// 允许递归, 则进行递归扫描
					if (recursive) findByFile(fname, file.getAbsolutePath(), true, pred, onRead);
				}
			}
		}
	}

	// 在jar文件中查找
	private static void findByJar(URL url, String pkgDir, boolean recursive, Predicate<String> pred, OnRead onRead) {
		JarFile jar;
		try {
			jar = ((JarURLConnection) url.openConnection()).getJarFile();
		} catch (IOException e) { return; }

		Enumeration<JarEntry> entries = jar.entries();
		int pkg_dir_len = pkgDir.length();

		// 进行循环迭代
		while (entries.hasMoreElements()) {
			// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
			JarEntry entry = entries.nextElement();
			if (entry.isDirectory()) continue;
			// 如果前半部分和定义的包名不同则忽略
			String name = entry.getName();
			if (!name.startsWith(pkgDir)) continue;
			else name = name.substring(pkg_dir_len + 1);
			// 如果不可递归, 则忽略子目录下的文件
			if (!recursive && name.indexOf('/', pkg_dir_len) != -1)
				continue;

			if ((pred == null || pred.test(name)) && onRead != null) {
				try (InputStream is = jar.getInputStream(entry)) {
					onRead.accept(is);
				} catch (IOException ignored) { }
			}
		}
	}

}
